﻿using System;
using System.Collections;
using System.Diagnostics;
using System.Threading;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Threading;
using System.Linq;

namespace KineticListView
{
    public class KineticListView : ListView
    {

        #region Constructors

        public KineticListView()
        {
            this.Loaded += new RoutedEventHandler(KineticListView_Loaded);
        }

        /// <summary>
        /// Handles the Loaded event of the KineticListView control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        void KineticListView_Loaded(object sender, RoutedEventArgs e)
        {
            if (Circular)
            {
                PositionToElement(CircularRepetitions, false);
            }
        }

        #endregion

        #region properties

        private ScrollViewer TemplateScrollViewer { get; set; }

        public KineticDirection Direction
        {
            get { return (KineticDirection)GetValue(DirectionProperty); }
            set { SetValue(DirectionProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Direction.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DirectionProperty =
             DependencyProperty.Register("Direction", typeof(KineticDirection), typeof(KineticListView), new UIPropertyMetadata(KineticDirection.Both));



        /// <summary>
        /// If True (default) selected item on the listview is hidden.
        /// </summary>
        /// <value>
        ///   <c>true</c> if [hide selection]; otherwise, <c>false</c>.
        /// </value>
        public Boolean HideSelection
        {
            get { return (Boolean)GetValue(HideSelectionProperty); }
            set { SetValue(HideSelectionProperty, value); }
        }

        // Using a DependencyProperty as the backing store for HideSelection.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty HideSelectionProperty =
            DependencyProperty.Register("HideSelection", typeof(Boolean), typeof(KineticListView), new UIPropertyMetadata(true));



        public Boolean HypeEnabled
        {
            get { return (Boolean)GetValue(HypeEnabledProperty); }
            set { SetValue(HypeEnabledProperty, value); }
        }

        // Using a DependencyProperty as the backing store for HypeEnabled.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty HypeEnabledProperty =
             DependencyProperty.Register("HypeEnabled", typeof(Boolean), typeof(KineticListView), new UIPropertyMetadata(false));

        /// <summary>
        /// Tells if the navigation should be circular, to be circular we need to set a certain number of
        /// repetition.
        /// </summary>
        /// <value><c>true</c> if circular; otherwise, <c>false</c>.</value>
        public Boolean Circular
        {
            get { return (Boolean)GetValue(CircularProperty); }
            set { SetValue(CircularProperty, value); }
        }

        // Using a DependencyProperty as the backing store for HypeEnabled.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CircularProperty =
             DependencyProperty.Register("Circular", typeof(Boolean), typeof(KineticListView), new UIPropertyMetadata(false));



        public int CircularRepetitions
        {
            get { return (int)GetValue(CircularRepetitionsProperty); }
            set { SetValue(CircularRepetitionsProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CircularRepetitions.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CircularRepetitionsProperty =
             DependencyProperty.Register("CircularRepetitions", typeof(int), typeof(KineticListView), new UIPropertyMetadata(0));


        /// <summary>
        /// We could not use the standard <see cref="ListView.SelectedValue"/> property of the <see cref="ListView"/> because
        /// it is used internally to change the visualization of the control. The selectedvalue is always null and we need to 
        /// provide the selected value with another property called SelectedObject.
        /// </summary>
        /// <value>
        /// The selected object.
        /// </value>
        public Object SelectedObject
        {
            get
            {
                return (Object)GetValue(SelectedObjectProperty);
            }
            set
            {
                SetValue(SelectedObjectProperty, value);
            }
        }

        // Using a DependencyProperty as the backing store for SelectedObject.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SelectedObjectProperty =
            DependencyProperty.Register("SelectedObject", typeof(Object), typeof(KineticListView),
            new FrameworkPropertyMetadata(null)
                {
                    BindsTwoWayByDefault = true
                });


        #endregion

        #region Kinetic

        private Point _mouseDragStartPoint;
        private Point _scrollStartOffset;
        private DateTime _mouseDragStartOperations;

        private const Int32 HypeTime = 500;


        public Double AnimationSpeed
        {
            get { return (Double)GetValue(AnimationSpeedProperty); }
            set { SetValue(AnimationSpeedProperty, value); }
        }

        // Using a DependencyProperty as the backing store for AnimationSpeed.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AnimationSpeedProperty =
            DependencyProperty.Register("AnimationSpeed", typeof(Double), typeof(KineticListView), new UIPropertyMetadata(1.0));



        /// <summary>
        /// When true, the listview use animation to move to another element, when someone
        /// else change the <see cref="CurrentFirstElementIndex"/> property or magnetic should change element. 
        /// If False no animation is used to move elements.
        /// </summary>
        /// <value><c>true</c> if [animation enabled]; otherwise, <c>false</c>.</value>
        public Boolean AnimationEnabled
        {
            get { return (Boolean)GetValue(AnimationEnabledProperty); }
            set { SetValue(AnimationEnabledProperty, value); }
        }

        // Using a DependencyProperty as the backing store for AnimationEnabled.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AnimationEnabledProperty =
            DependencyProperty.Register("AnimationEnabled", typeof(Boolean), typeof(KineticListView), new UIPropertyMetadata(true));



        /// <summary>
        /// This is a specific property to fix a bug when the touchscreen of the device is not so good
        /// and sometimes raise a mouseup because it lost connection.
        /// </summary>
        /// <value>The mouse up disconnection interval.</value>
        public Int32 MouseUpDisconnectionInterval
        {
            get { return (Int32)GetValue(MouseUpDisconnectionIntervalProperty); }
            set { SetValue(MouseUpDisconnectionIntervalProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MouseUpDisconnectionInterval.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MouseUpDisconnectionIntervalProperty =
            DependencyProperty.Register("MouseUpDisconnectionInterval", typeof(Int32), typeof(KineticListView), new UIPropertyMetadata(0));



        /// <summary>
        /// tells if the control is in scrolling mode
        /// </summary>
        private Boolean _isScrolling = false;

        public static readonly DependencyProperty HorizontalOffsetOfTemplateScrollViewProperty =
             DependencyProperty.Register("HorizontalOffsetOfTemplateScrollView", typeof(double), typeof(KineticListView),
             new UIPropertyMetadata(AdjustTemplateScrollViewHorizontalOffset));

        public double HorizontalOffsetOfTemplateScrollView
        {
            get { return (double)GetValue(HorizontalOffsetOfTemplateScrollViewProperty); }

            set { SetValue(HorizontalOffsetOfTemplateScrollViewProperty, value); }
        }

        private static void AdjustTemplateScrollViewHorizontalOffset(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            KineticListView theList = (KineticListView)d;
            theList.TemplateScrollViewer.ScrollToHorizontalOffset((Double)e.NewValue);
        }

        public static readonly DependencyProperty VerticalOffsetOfTemplateScrollViewProperty =
             DependencyProperty.Register("VerticalOffsetOfTemplateScrollViewProperty", typeof(double), typeof(KineticListView),
             new UIPropertyMetadata(AdjustTemplateScrollViewVerticalOffset));

        public double VerticalOffsetOfTemplateScrollView
        {
            get { return (double)GetValue(VerticalOffsetOfTemplateScrollViewProperty); }

            set { SetValue(VerticalOffsetOfTemplateScrollViewProperty, value); }
        }

        private static void AdjustTemplateScrollViewVerticalOffset(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            KineticListView theList = (KineticListView)d;
            theList.TemplateScrollViewer.ScrollToVerticalOffset((Double)e.NewValue);
        }

        private void SetMagnetic()
        {
            int currentElementIndex = 0;
            if (Direction == KineticDirection.Horizontal)
            {
                currentElementIndex = ElementAtX();
            }
            else if (Direction == KineticDirection.Vertical)
            {
                currentElementIndex = ElementAtY();
            }
            if (currentElementIndex == CurrentFirstElementIndex)
            {
                //no change of element, simply position to current element
                PositionToElement(CurrentFirstElementIndex, true);
            }
            else
            {
                //moving the current already position to current element
                CurrentFirstElementIndex = currentElementIndex;
            }
        }

        /// <summary>
        /// Reset the animation status, and most important, after the animation is complete we need to 
        /// handle circular trick.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        void CurrentAnimationCompleted(object sender, EventArgs e)
        {
            if (_currentAnimation == null) return;
            _currentAnimation.Completed -= CurrentAnimationCompleted;
            _currentAnimation = null;
            if (Circular)
            {
                //I'm in circular mode, so I need to check if I pass more than half of the exeeding element
                if (CurrentFirstElementIndex < Math.Ceiling(CircularRepetitions / 2.0f))
                {
                    //Need to swap to the end of the list
                    int elementIndex = Items.Count - CircularRepetitions * 2 + CurrentFirstElementIndex;
                    this.PositionToElement(elementIndex, false);
                    this._doNotSyncUiWithCurrentElement = true;
                    CurrentFirstElementIndex = elementIndex;
                }
                else if (CurrentFirstElementIndex + NumOfVisibleItems() > Items.Count - CircularRepetitions / 2)
                {
                    int elementIndex = CurrentFirstElementIndex - (Items.Count - CircularRepetitions * 2);
                    this.PositionToElement(elementIndex, false);
                    this._doNotSyncUiWithCurrentElement = true;
                    CurrentFirstElementIndex = elementIndex;
                }
            }
        }


        /// <summary>
        /// Moves to another element, with magnetic and animation.
        /// </summary>
        /// <param name="xOfelement">The elemtn.</param>
        /// <param name="useAnimation"></param>
        private void MoveToElementX(int xOfelement, bool useAnimation)
        {
            if (TemplateScrollViewer == null) return;
            double itemTemplateWidth = GetItemTemplateWidth();
            if (itemTemplateWidth == 0)
            {
                //probably we will have a problem trying to move element before
                //dimension are calculated.
                Dispatcher.BeginInvoke(
                    (Action)(() => MoveToElementX(xOfelement, useAnimation)),
                    DispatcherPriority.Background);
                return;
            }
            var newOffset = itemTemplateWidth * xOfelement;
            Int32 distance = (Int32)Math.Abs(TemplateScrollViewer.HorizontalOffset - newOffset);
            if (useAnimation)
            {
                CreateAnimation(TemplateScrollViewer.HorizontalOffset, newOffset, distance);
                this.BeginAnimation(HorizontalOffsetOfTemplateScrollViewProperty, _currentAnimation);
            }
            else
            {
                TemplateScrollViewer.ScrollToHorizontalOffset(newOffset);
            }
        }

        private void MoveToElementY(int yOfElement, bool useAnimation)
        {
            if (TemplateScrollViewer == null) return;
            double itemTemplateWidth = GetItemTemplateHeight();
            if (itemTemplateWidth == 0)
            {
                //probably we will have a problem trying to move element before
                //dimension are calculated.
                Dispatcher.BeginInvoke(
                    (Action)(() => MoveToElementX(yOfElement, useAnimation)),
                    DispatcherPriority.Background);
                return;
            }
            var newOffset = itemTemplateWidth * yOfElement;
            Int32 distance = (Int32)Math.Abs(TemplateScrollViewer.VerticalOffset - newOffset);
            if (useAnimation)
            {
                CreateAnimation(TemplateScrollViewer.VerticalOffset, newOffset, distance);
                BeginAnimation(VerticalOffsetOfTemplateScrollViewProperty, _currentAnimation);
            }
            else
            {
                TemplateScrollViewer.ScrollToVerticalOffset(newOffset);
            }
        }

        /// <summary>
        /// Creates the animation and store it into an inner property
        /// </summary>
        /// <param name="scrollStart">The scroll start.</param>
        /// <param name="scrollEnd">The scroll end.</param>
        /// <param name="distance">The distance.</param>
        private void CreateAnimation(double scrollStart, double scrollEnd, Int32 distance)
        {
            _currentAnimation = new DoubleAnimation();

            _currentAnimation.Completed += new EventHandler(CurrentAnimationCompleted);
            _currentAnimation.From = scrollStart;
            _currentAnimation.To = scrollEnd;

            //Animation speed should be based even on how much pixel we want to scroll.
            //do not want an animation to elapse more than 5 seconds
            Int32 duration = (Int32)Math.Min(2 * distance / AnimationSpeed, 5000);
            _currentAnimation.DecelerationRatio = Math.Min(1.0, 5000.0 / duration);
            _currentAnimation.SpeedRatio = 1.0;

            _currentAnimation.Duration = new Duration(new TimeSpan(0, 0, 0, 0, duration));
        }

        protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                _mouseDragStartPoint = e.GetPosition(this);
                _mouseDragStartOperations = DateTime.Now;
                _scrollStartOffset.X = TemplateScrollViewer.HorizontalOffset;
                _scrollStartOffset.Y = TemplateScrollViewer.VerticalOffset;
                //Debug.WriteLine("Started drag at " + scrollStartOffset.X);
            }

            base.OnPreviewMouseDown(e);
        }

        private void UpdateCursorBasedOnCurrentScrollCapacity()
        {
            Boolean horizontalScroll = (TemplateScrollViewer.ExtentWidth > TemplateScrollViewer.ViewportWidth &&
                         (Direction & KineticDirection.Horizontal) > 0);
            Boolean verticalScroll = (TemplateScrollViewer.ExtentHeight > TemplateScrollViewer.ViewportHeight &&
                         (Direction & KineticDirection.Vertical) > 0);
            if (verticalScroll && horizontalScroll)
                this.Cursor = Cursors.ScrollAll;
            else if (horizontalScroll)
                this.Cursor = Cursors.ScrollWE;
            else if (verticalScroll)
                this.Cursor = Cursors.ScrollNS;
        }

        protected override void OnPreviewMouseMove(MouseEventArgs e)
        {
            //le operazioni di scroll iniziano solamente se l'offset è maggiore di un certo tot,
            //per evitare di iniziare delle operazioni di microscroll e di evitare che siano premuti i bottoni
            if (e.LeftButton == MouseButtonState.Pressed && !_isScrolling)
            {

                //ho il bottone sinistro premuto, ma non sto ancora scrollando, voglio capire 
                //se ho spostato più di un certo offset.
                var position = e.GetPosition(this);
                if (CanStartScrollOperation(position))
                {
                    _isScrolling = true;
                    //memorizzo l'elemento selezionato
                    _selectedItemAtStartDragOperation = this.SelectedItem;
                    UpdateCursorBasedOnCurrentScrollCapacity();


                }
            }
            //procedo con il drag solamente se sto scrollando
            if (e.LeftButton == MouseButtonState.Pressed && _isScrolling)
            {
                if (IsMouseActualOver(e.GetPosition(this)))
                {
                    if (!(IsMouseCaptured))
                    {
                        this.CaptureMouse();
                    }
                    // Get the new mouse position. 
                    Point mouseDragCurrentPoint = e.GetPosition(this);

                    // Determine the new amount to scroll. 
                    Point delta = new Point(
                         GetOffsetX(mouseDragCurrentPoint),
                         GetOffsetY(mouseDragCurrentPoint));

                    // Scroll to the new position. 
                    ScrollTheTemplate(delta);
                }
                else
                {
                    // Set isScrolling to false and update the cursor
                    this.Cursor = Cursors.Arrow;
                    SetMagnetic();
                    // Release mouse capture when the mouse is not over the control
                    // did this to disable scrolling while mouse is not over the control
                    if (this.IsMouseCaptured)
                    {
                        this.ReleaseMouseCapture();
                    }
                }
            }
            base.OnPreviewMouseMove(e);
        }

        private bool CanStartScrollOperation(Point position)
        {
            return MouseMovementGreaterThanBias(position, 20);
        }

        private bool MouseMovementGreaterThanBias(Point position, int biasValue)
        {
            return (Math.Abs(_mouseDragStartPoint.X - position.X) > biasValue && Direction == KineticDirection.Horizontal)
                     || (Math.Abs(_mouseDragStartPoint.Y - position.Y) > biasValue && Direction == KineticDirection.Vertical);
        }

        private Int32 GetAmplitude(Point position)
        {
            if (Direction == KineticDirection.Horizontal)

                return (Int32)(_mouseDragStartPoint.X - position.X);
            else
                return (Int32)(_mouseDragStartPoint.Y - position.Y);
        }

        private double GetOffsetY(Point mouseDragCurrentPoint)
        {
            if ((Direction & KineticDirection.Vertical) > 0)
                return (mouseDragCurrentPoint.Y > this._mouseDragStartPoint.Y)
                              ? -(mouseDragCurrentPoint.Y - this._mouseDragStartPoint.Y)
                              : (this._mouseDragStartPoint.Y - mouseDragCurrentPoint.Y);

            return 0;
        }

        private double GetOffsetX(Point mouseDragCurrentPoint)
        {
            if ((Direction & KineticDirection.Horizontal) > 0)
                return (mouseDragCurrentPoint.X > this._mouseDragStartPoint.X)
                              ? -(mouseDragCurrentPoint.X - this._mouseDragStartPoint.X)
                              : (this._mouseDragStartPoint.X - mouseDragCurrentPoint.X);

            return 0;
        }

        private void ScrollTheTemplate(Point delta)
        {
            if (_currentAnimation != null)
            {
                this.BeginAnimation(HorizontalOffsetOfTemplateScrollViewProperty, null);
                Thread.Sleep(100);
            }
            TemplateScrollViewer.ScrollToHorizontalOffset(this._scrollStartOffset.X + delta.X);
            TemplateScrollViewer.ScrollToVerticalOffset(this._scrollStartOffset.Y + delta.Y);
            //Debug.WriteLine(string.Format("scrolled delta ({0} , {1})", delta.X, delta.Y));

        }


        private bool IsMouseActualOver(Point p)
        {
            if ((p.X > 1 && p.X < this.ActualWidth - 1) &&
                 (p.Y > 1 && p.Y < this.ActualHeight - 1))
                return true;
            else
                return false;
        }

        private System.Timers.Timer _mouseUpTimer;
        private MouseButtonEventArgs _lastMouseUpButtonEventArgs;
        private void InitTimer()
        {
            _mouseUpTimer = new System.Timers.Timer();
            _mouseUpTimer.Interval = MouseUpDisconnectionInterval;
            _mouseUpTimer.Elapsed += MouseUpTimerElapsed;
            _mouseUpTimer.AutoReset = false;
            _mouseUpTimer.Start();
            Debug.WriteLine("Timer inited");
        }

        private void MouseUpTimerElapsed(object sender, ElapsedEventArgs e)
        {
            Debug.WriteLine("Timer elapsed");

            if (this.CheckAccess())
            {
                ExecuteMouseUp();
            }
            else
            {
                Dispatcher.Invoke((Action)ExecuteMouseUp);
            }

        }

        private void ExecuteMouseUp()
        {
            if (_lastMouseUpButtonEventArgs == null ||
                Mouse.LeftButton == MouseButtonState.Pressed) return;
            MouseUpKineticLogic(_lastMouseUpButtonEventArgs);
            _lastMouseUpButtonEventArgs = null;
        }


        /// <summary>
        /// Need to intercept the mouse up to finish scroll operation. Remember that
        /// the property <see cref="MouseUpDisconnectionInterval"/> is used to handle
        /// finger disconnection on not-so-good touch screen.
        /// </summary>
        protected override void OnPreviewMouseUp(MouseButtonEventArgs e)
        {
            if (MouseUpDisconnectionInterval > 0)
            {
                if (_lastMouseUpButtonEventArgs == null)
                {
                    //this is the first mouse up, we need to start the timer
                    _lastMouseUpButtonEventArgs = e;
                    InitTimer();

                }
                else
                {
                    //this is another mouse up that is raised while the timer is running.
                    //simply update it, and reinit timer
                    _lastMouseUpButtonEventArgs = e;
                    _mouseUpTimer.Stop();
                    _mouseUpTimer.Start();
                }

            }
            else
            {
                MouseUpKineticLogic(e);
            }

            base.OnPreviewMouseUp(e);
        }

        private void MouseUpKineticLogic(MouseButtonEventArgs e)
        {
            if (this.IsMouseCaptured)
            {
                this.Cursor = Cursors.Arrow;
                this.ReleaseMouseCapture();

            }
            if (_isScrolling && HypeEnabled)
            {
                //I've did some sort of scroll operation, we need to understand if an hype is done
                Point position = e.GetPosition(this);
                Int32 offset = GetAmplitude(position);
                Int32 bias;
                if (Direction == KineticDirection.Horizontal)
                    bias = (Int32)GetItemTemplateWidth() / 4;
                else
                    bias = (Int32)GetItemTemplateHeight() / 4;
                if (offset > bias &&
                    DateTime.Now.Subtract(_mouseDragStartOperations).TotalMilliseconds < HypeTime)
                {
                    //An hype is done, need to move the element instead of setting the magnetic
                    //the animation is ruled by the CurrentFirstElementIndex.
                    this.CurrentFirstElementIndex += offset > 0 ? +1 : -1;
                }
                else
                {
                    SetMagnetic();
                }
            }
            else
            {
                SetMagnetic();
            }

            _isScrolling = false;
        }

        #endregion

        #region Selection handling

        private Object _selectedItemAtStartDragOperation;

        private Boolean _guardOnSelectionChanged;
        protected override void OnSelectionChanged(SelectionChangedEventArgs e)
        {
            e.Handled = true;
            if (!_guardOnSelectionChanged)
            {
                _guardOnSelectionChanged = true;
                try
                {
                    if (!_isScrolling)
                    {
                        SelectedObject = this.SelectedItem;
                    }
                    Debug.WriteLine("SelectinChanged");
                    if (HideSelection)
                    {
                        this.SelectedItem = null;
                    }
                    else
                    {
                        //I should not hide selection, but we need to be sure that when the
                        //kinetic is dragging around no selection is changed.
                        if (_isScrolling)
                        {
                            this.SelectedItem = _selectedItemAtStartDragOperation;
                        }
                    }
                }
                finally
                {
                    _guardOnSelectionChanged = false;
                }
            }

            base.OnSelectionChanged(e);

        }

        #endregion

        #region Scroll Viewer Manipulation

        public void ResetLayout()
        {
            if (TemplateScrollViewer == null)
            {
                //I have no Template scrollviewer
                _doNotSyncUiWithCurrentElement = false;
                return;
            }

            TemplateScrollViewer.ScrollToHorizontalOffset(0);
            TemplateScrollViewer.ScrollToVerticalOffset(0);
            CurrentFirstElementIndex = 0;
        }

        /// <summary>
        /// Search for the first visual child of T found
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="element"></param>
        /// <returns></returns>
        private T GetVisualChild<T>(DependencyObject element) where T : Visual
        {
            if (element == null) return null;

            T foundElement = element as T;

            if (foundElement != null) return foundElement;

            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
            {
                DependencyObject childElement = VisualTreeHelper.GetChild(element, i);
                foundElement = GetVisualChild<T>(childElement);
                if (foundElement != null)
                    break;
            }

            return foundElement;
        }

        public override void OnApplyTemplate()
        {
            if (Direction == KineticDirection.Horizontal)
            {
                ItemsPanelTemplate newTemplate = new ItemsPanelTemplate();
                FrameworkElementFactory factory = new FrameworkElementFactory(typeof(StackPanel));
                factory.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
                newTemplate.VisualTree = factory;
                this.ItemsPanel = newTemplate;
            }

            base.OnApplyTemplate();

            // Setup listViewScrollViewer
            DependencyObject border = GetTemplateChild("Bd");
            TemplateScrollViewer = GetVisualChild<ScrollViewer>(border);
            if (TemplateScrollViewer != null)
            {
                // Diable the ScrollBars.  Setting CanContentScroll to false disables
                // default scrolling behavior which uses a VirtualizingStackPanel.
                TemplateScrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden;
                TemplateScrollViewer.VerticalScrollBarVisibility = ScrollBarVisibility.Hidden;
                TemplateScrollViewer.CanContentScroll = false;
            }
        }

        #endregion

        #region Item Management

        /// <summary>
        /// First element that is actually visualized in the listview.
        /// </summary>
        /// <value>
        /// The index of the current first element.
        /// </value>
        public int CurrentFirstElementIndex
        {
            get
            {
                return (int)GetValue(CurrentFirstElementIndexProperty);
            }
            set
            {
                SetValue(CurrentFirstElementIndexProperty, value);
            }
        }

        // Using a DependencyProperty as the backing store for CurrentFirstElementIndex.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CurrentFirstElementIndexProperty =
             DependencyProperty.Register("CurrentFirstElementIndex",
             typeof(int), typeof(KineticListView),
             new UIPropertyMetadata(0, OnCurrentFirstElementIndexChanged));

        private DoubleAnimation _currentAnimation;

        private Boolean _doNotSyncUiWithCurrentElement = false;
        /// <summary>
        /// When property of CurrentFirstElement changes it reflect the change in the ui.
        /// </summary>
        /// <param name="d">The d.</param>
        /// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
        public static void OnCurrentFirstElementIndexChanged(
             DependencyObject d,
             DependencyPropertyChangedEventArgs e)
        {
            KineticListView lv = (KineticListView)d;
            if ((Int32)e.NewValue >= 0)
            {
                if (!lv._doNotSyncUiWithCurrentElement)
                {
                    lv.PositionToElement(lv.CurrentFirstElementIndex, true);
                }
                lv._doNotSyncUiWithCurrentElement = false;
            }
            else
            {
                //if index is less than zero we want to reset the view.
                lv._doNotSyncUiWithCurrentElement = true;
                lv.ResetLayout();
            }

        }

        private void PositionToElement(int elementIndex, bool useAnimation)
        {
            if (Items.Count == 0) return;

            if (Direction == KineticDirection.Horizontal)
                MoveToElementX(elementIndex, useAnimation && AnimationEnabled);
            else
                MoveToElementY(elementIndex, useAnimation && AnimationEnabled);
        }

        private Int32 ElementAtX()
        {

            double actualWidth = GetItemTemplateWidth();
            return (Int32)Math.Round(TemplateScrollViewer.ContentHorizontalOffset / actualWidth);
        }

        private Int32 ElementAtY()
        {
            double actualHeight = GetItemTemplateHeight();
            return (Int32)Math.Round(TemplateScrollViewer.ContentVerticalOffset / actualHeight);
        }

        private double GetItemTemplateWidth()
        {
            var lvi = (ListViewItem)this.ItemContainerGenerator.ContainerFromIndex(0);
            //simple math, to calculate the element that is actually more visible on the left border
            if (lvi == null) return 0;
            return lvi.ActualWidth;
        }

        private double NumOfVisibleItems()
        {
            if (Direction == KineticDirection.Horizontal)
            {
                return ActualWidth / GetItemTemplateWidth();
            }
            else
            {
                return ActualHeight / GetItemTemplateHeight();
            }
        }

        private double GetItemTemplateHeight()
        {
            var lvi = (ListViewItem)this.ItemContainerGenerator.ContainerFromIndex(0);
            //simple math, to calculate the element that is actually more visible on the left border
            if (lvi == null) return 0;
            return lvi.ActualHeight;
        }

        #endregion

    }
}
